/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
#include "commonheaders.h"
#include "fileXML.h"
#include "rfcCodecs.h"


namespace NExImport {

/***********************************************************************************************************
 * common stuff
 ***********************************************************************************************************/

/**
 * name:	SortProc
 * desc:	used for bsearch in CExImContactXML::IsContactInfo
 * param:	item1	- item to compare
 *			item2	- item to compare
 * return:	the difference
 **/
static INT SortProc(const LPDWORD item1, const LPDWORD item2)
{
	return *item1 - *item2;
}

/**
 * name:	CExImContactXML
 * class:	CExImContactXML
 * desc:	the constructor for the contact class
 * param:	pXmlFile	- the owning xml file
 * return:	nothing
 **/
CExImContactXML::CExImContactXML(CFileXml * pXmlFile)
	: CExImContactBase()
{
	_xmlNode = NULL;
	_pXmlFile = pXmlFile;
}

/**
 * name:	IsContactInfo
 * class:	CExImContactXML
 * desc:	this function compares the given setting key to the list of known contact
 *			information keys
 * param:	pszKey	- the settings key to check
 * return:	TRUE if pszKey is a valid contact information
 **/
BOOLEAN CExImContactXML::IsContactInfo(LPCSTR pszKey)
{
	// This is a sorted list of all hashvalues of the contact information.
	// This is the same as the szCiKey[] array below but sorted
	const DWORD dwCiHash[] = {
		0xE263B4C7,0xE2D4B620,0xE9338E02,0xEE6346F3,0xF6A29A30,0xFA011CE3,0x00019881,
		0x0001CD5A,0x0001EC46,0x003D5D43,0x031BC050,0x032BC050,0x033BC050,0x05333AB9,
		0x0562047D,0x0562057D,0x0562067D,0x0666B170,0x0667DD54,0x066B8AF3,0x077B3021,
		0x0D763751,0x1AAD7D7F,0x1B38C893,0x1CB6B229,0x213AFB93,0x3183AE45,0x32B3D35D,
		0x369B0C15,0x369B0E15,0x369B0F15,0x4CDBC44C,0x4EDACEC0,0x59933A67,0x5AA25979,
		0x5B9D88F1,0x611443C1,0x6F2037B3,0x6F2037D1,0x6F2037D3,0x7089EC12,0x7089EE12,
		0x7089EF12,0x819F7570,0x84DBC37F,0x8C409E19,0x8D409E19,0x8DC09E19,0x8EBBC692,
		0x9A26DBAD,0x9A27D34D,0x9B782336,0x9BC35385,0x9EA131C9,0x9F67BF4B,0xA376D99D,
		0xA91C321D,0xA975EE05,0xAB1BB789,0xAB2BB789,0xAB3BB789,0xB27F9C48,0xB39CDB4B,
		0xB3B06C55,0xB4BBB453,0xCC4320D4,0xCD4320D4,0xCDC320D4,0xCE2913C9,0xDEA131C5,
		0xDEA131C9
	};
	if(pszKey && *pszKey) {
		char buf[MAXSETTING];
		// convert to hash and make bsearch as it is much faster then working with strings
		const DWORD dwHash = hashSetting(_strlwr(mir_strncpy(buf, pszKey, SIZEOF(buf))));
		return bsearch(&dwHash, dwCiHash, SIZEOF(dwCiHash), sizeof(dwCiHash[0]), (INT (*)(const VOID*, const VOID*))SortProc) != NULL;
	}
	return FALSE;
/*
	WORD i;
	const LPCSTR szCiKey[] = {
		// naming
		SET_CONTACT_TITLE,SET_CONTACT_FIRSTNAME,SET_CONTACT_SECONDNAME,SET_CONTACT_LASTNAME,SET_CONTACT_PREFIX,
		// private address
		SET_CONTACT_STREET,SET_CONTACT_ZIP,SET_CONTACT_CITY,SET_CONTACT_STATE,SET_CONTACT_COUNTRY,
		SET_CONTACT_PHONE,SET_CONTACT_FAX,SET_CONTACT_CELLULAR,
		SET_CONTACT_EMAIL,SET_CONTACT_EMAIL0,SET_CONTACT_EMAIL1,SET_CONTACT_HOMEPAGE,
		// origin
		SET_CONTACT_ORIGIN_STREET,SET_CONTACT_ORIGIN_ZIP,SET_CONTACT_ORIGIN_CITY,SET_CONTACT_ORIGIN_STATE,SET_CONTACT_ORIGIN_COUNTRY,
		// company
		SET_CONTACT_COMPANY_POSITION,SET_CONTACT_COMPANY_OCCUPATION,SET_CONTACT_COMPANY_SUPERIOR,SET_CONTACT_COMPANY_ASSISTENT
		SET_CONTACT_COMPANY,SET_CONTACT_COMPANY_DEPARTMENT,SET_CONTACT_COMPANY_OFFICE,
		SET_CONTACT_COMPANY_STREET,SET_CONTACT_COMPANY_ZIP,SET_CONTACT_COMPANY_CITY,SET_CONTACT_COMPANY_STATE,SET_CONTACT_COMPANY_COUNTRY,
		SET_CONTACT_COMPANY_PHONE,SET_CONTACT_COMPANY_FAX,SET_CONTACT_COMPANY_CELLULAR,
		SET_CONTACT_COMPANY_EMAIL,SET_CONTACT_COMPANY_EMAIL0,SET_CONTACT_COMPANY_EMAIL1,SET_CONTACT_COMPANY_HOMEPAGE,
		// personal information
		SET_CONTACT_ABOUT,SET_CONTACT_MYNOTES,SET_CONTACT_MARITAL,SET_CONTACT_PARTNER,
		SET_CONTACT_LANG1,SET_CONTACT_LANG2,SET_CONTACT_LANG3,SET_CONTACT_TIMEZONE,SET_CONTACT_AGE,SET_CONTACT_GENDER,
		SET_CONTACT_BIRTHDAY,SET_CONTACT_BIRTHMONTH,SET_CONTACT_BIRTHYEAR,
		"Past0", "Past0Text","Past1", "Past1Text","Past2", "Past2Text",
		"Affiliation0", "Affiliation0Text","Affiliation1", "Affiliation1Text","Affiliation2", "Affiliation2Text",
		"Interest0Cat", "Interest0Text","Interest1Cat", "Interest1Text","Interest2Cat", "Interest2Text"
	};
	DWORD *hash = new DWORD[SIZEOF(szCiKey)];
	char buf[MAX_PATH];

	for(i = 0; i < SIZEOF(szCiKey); i++) {
		strcpy(buf, szCiKey[i]);
		hash[i] = hashSetting(_strlwr((char*)buf));
	}
	qsort(hash, SIZEOF(szCiKey), sizeof(hash[0]), 
	(INT (*)(const VOID*, const VOID*))SortProc);
	
	FILE* fil = fopen("D:\\temp\\id.txt", "wt");
	for(i = 0; i < SIZEOF(szCiKey); i++) {
		fprintf(fil, "0x%08X,", hash[i]);
	}
	fclose(fil);
	return FALSE;
	*/
}

/***********************************************************************************************************
 * exporting stuff
 ***********************************************************************************************************/

/**
 * name:	CreateXmlNode
 * class:	CExImContactXML
 * desc:	creates a new TiXmlElement representing the contact
 *			whose information are stored in this class
 * param:	none
 * return:	pointer to the newly created TiXmlElement
 **/
TiXmlElement* CExImContactXML::CreateXmlElement()
{
	if(_hContact) {
		if(proto()) {
			_xmlNode = new TiXmlElement(XKEY_CONTACT);
			
			if(_xmlNode) {
				LPSTR pszUID = uid2String(TRUE);

				if(disp()) _xmlNode->SetAttribute("disp", disp());
				if(nick()) _xmlNode->SetAttribute("nick", nick());
				if(group()) _xmlNode->SetAttribute("group", group());
				
				_xmlNode->SetAttribute("proto", proto());
				if(pszUID) {

					if(uidk()) {
						_xmlNode->SetAttribute("uidk", uidk());
						_xmlNode->SetAttribute("uidv", pszUID);
					}
					mir_free(pszUID);
				}
			}
		}
		else
			_xmlNode = NULL;
	}
	else {
		_xmlNode = new TiXmlElement(XKEY_OWNER);
	}
	return _xmlNode;
}

/**
 * name:	ExportContact
 * class:	CExImContactXML
 * desc:	exports a contact
 * param:	none
 * return:	ERROR_OK on success or any other on failure
 **/
INT CExImContactXML::ExportContact(LPENUMLIST pModules)
{
	LPENUMLISTITEM mod;

	if(_pXmlFile->_wExport & EXPORT_DATA) {
		if(pModules) {
			// add all modules to the current contact
			for(mod = pModules->first; mod != NULL; mod = mod->next) {
				// in no circumstance the following two protocols should be exported
				if(!mir_stricmp(mod->pszName, "Protocol")) continue;
				if(!mir_stricmp(mod->pszName, META_PROTO)) continue;
				ExportModule(mod->pszName);
			}
		}
		else {
			ExportModule(USERINFO);
			ExportModule(MOD_MBIRTHDAY);
		}
	}
	// export contact's events
	if(_pXmlFile->_wExport & EXPORT_HISTORY)
		ExportEvents();

	return ERROR_OK;
}

/**
 * name:	ExportSubContact
 * class:	CExImContactXML
 * desc:	exports a meta sub contact
 * param:	none
 * return:	ERROR_OK on success or any other on failure
 **/
INT CExImContactXML::ExportSubContact(CExImContactXML *vMetaContact, LPENUMLIST pModules)
{
	// create xmlNode
	if(!CreateXmlElement()) 
		return ERROR_INVALID_CONTACT;
	if(ExportContact(pModules) == ERROR_OK) {
		if(!_xmlNode->NoChildren() && vMetaContact->_xmlNode->LinkEndChild(_xmlNode)) {
			return ERROR_OK;
		}
	}
	if(_xmlNode) delete _xmlNode;
	return ERROR_NOT_ADDED;
}

/**
 * name:	Export
 * class:	CExImContactXML
 * desc:	exports a contact
 * param:	xmlfile		- handle to the open file to write the contact to
 *			pModules	- list of modules to export for each contact
 * return:	ERROR_OK on success or any other on failure
 **/
INT CExImContactXML::Export(FILE *xmlfile, LPENUMLIST pModules)
{
	if(!xmlfile) 
		return ERROR_INVALID_PARAMS;
	if(_hContact == INVALID_HANDLE_VALUE)
		return ERROR_INVALID_CONTACT;
	// create xmlNode
	if(!CreateXmlElement()) 
		return ERROR_INVALID_CONTACT;

	// export meta
	if(isMeta()) {
		CExImContactXML vContact(_pXmlFile);		

		INT i, numSubs, iDefault = DBGetSettingDword(_hContact, META_PROTO, SET_META_DEFAULTNUM, -1);
		HANDLE hSubContact = (HANDLE)CallService(MS_MC_GETSUBCONTACT, (WPARAM)_hContact, iDefault);

		// export default subcontact
		if(hSubContact && vContact.fromDB(hSubContact))
			vContact.ExportSubContact(this, pModules);

		numSubs = DBGetSettingDword(_hContact, META_PROTO, SET_META_NUMCONTACTS, -1);
		for(i = 0; i < numSubs; i++) {
			if(i == iDefault) continue;
			if(!(hSubContact = (HANDLE)CallService(MS_MC_GETSUBCONTACT, (WPARAM)_hContact, i))) continue;
			if(hSubContact && vContact.fromDB(hSubContact))
				vContact.ExportSubContact(this, pModules);
		}
	}
	ExportContact(pModules);

	// add xContact to document
	if(_xmlNode->NoChildren()) {
		delete _xmlNode;
		_xmlNode = NULL;
		return ERROR_NOT_ADDED;
	}
	_xmlNode->Print(xmlfile, 1);
	fputc('\n', xmlfile);
	delete _xmlNode;
	_xmlNode = NULL;
	return ERROR_OK;
}

/**
 * name:	ExportModule
 * class:	CExImContactXML
 * desc:	enumerates all settings of a database module and adds them to the xml tree
 * params:	pszModule	- the module which is to export
 * return:	ERROR_OK on success or any other on failure
 **/
INT CExImContactXML::ExportModule(LPCSTR pszModule)
{
	ENUMLIST li;
	LPENUMLISTITEM key;
	TiXmlElement *xmod;

	if(!pszModule || !*pszModule)
		return ERROR_INVALID_PARAMS;
	if(!(xmod = new TiXmlElement(XKEY_MOD)))
		return ERROR_MEMORY_ALLOC;
	if(!DBEnumSettings(_hContact, pszModule, &li)) {
		delete xmod;
		return ERROR_EMPTY_MODULE;
	}
	xmod->SetAttribute("key", pszModule);
	for(key = li.first; key != NULL; key = key->next) {
		ExportSetting(xmod, pszModule, key->pszName);
	}
	DBFreeEnumList(&li);
	if(xmod->NoChildren() || !_xmlNode->LinkEndChild(xmod)) {
		delete xmod;
		return ERROR_EMPTY_MODULE;
	}
	return ERROR_OK;
}

/**
 * name:	ExportSetting
 * desc:	read a setting from database and add an xmlelement to contact node
 * params:	xmlModule	- xml node to add the setting to
 *			hContact	- handle of the contact whose event chain is to export
 *			pszModule	- the module which is to export
 *			pszSetting	- the setting which is to export
 * return:	pointer to the added element
 **/
INT CExImContactXML::ExportSetting(TiXmlElement *xmlModule, LPCSTR pszModule, LPCSTR pszSetting)
{
	DBVARIANT dbv;
	TiXmlElement *xmlEntry = NULL;
	TiXmlText *xmlValue = NULL;
	CHAR buf[32];

	if(DBGetSettingUtf8(_hContact, pszModule, pszSetting, &dbv))
		return ERROR_INVALID_VALUE;
	switch(dbv.type) {
		case DBVT_BYTE:
			buf[0] = 'b';
			_ultoa(dbv.bVal, buf + 1, 10);
			xmlValue = new TiXmlText(buf);
			break;
		case DBVT_WORD:
			buf[0] = 'w';
			_ultoa(dbv.wVal, buf + 1, 10);
			xmlValue = new TiXmlText(buf);
			break;
		case DBVT_DWORD:
			buf[0] = 'd';
			_ultoa(dbv.dVal, buf + 1, 10);
			xmlValue = new TiXmlText(buf);
			break;
		case DBVT_UTF8:	{
			LPSTR str;
			DWORD len = mir_strlen(dbv.pszVal);
			if(len > 0 && (str = (LPSTR)_alloca(len + 2))) {
				str[0] = 'u';
				mir_strcpy(&str[1], dbv.pszVal);
				xmlValue = new TiXmlText(str);
			}
		}
	}
	DBFreeVariant(&dbv);
	if(xmlValue) {
		xmlEntry = new TiXmlElement(XKEY_SET);
		if(xmlEntry) {
			xmlEntry->SetAttribute("key", pszSetting);
			if(xmlEntry->LinkEndChild(xmlValue) && xmlModule->LinkEndChild(xmlEntry))
				return ERROR_OK;
			delete xmlEntry;
		}
		delete xmlValue;
	}
	return ERROR_MEMORY_ALLOC;
}

/**
 * name:	ExportEvents
 * desc:	adds the event chain for a given contact to the xml tree
 * params:	xContact	- the xml node to add the events as childs to
 *			hContact	- handle of the contact whose event chain is to export
 * return:	TRUE on success, FALSE otherwise
 **/
BOOLEAN CExImContactXML::ExportEvents()
{
	DBEVENTINFO	dbei; 
	HANDLE		hDbEvent;
	PBYTE		pbEventBuf = NULL;
	DWORD		cbEventBuf = 0,
				dwNumEvents = 0,
				dwNumEventsAdded = 0;
	LPSTR		pBase64Data = NULL;
	INT			cbBase64Data = 0,
				cbNewBase64Data = 0;

	TiXmlNode	 *xmlModule = NULL;
	TiXmlElement *xmlEvent = NULL;
	TiXmlText	 *xmlText = NULL;

	dwNumEvents = CallService(MS_DB_EVENT_GETCOUNT, (WPARAM)_hContact, NULL);
	if(dwNumEvents == 0) return FALSE;

	try {

		ZeroMemory(&dbei, sizeof(DBEVENTINFO));
		dbei.cbSize = sizeof(DBEVENTINFO);
	
		// read out all events for the current contact
		for(hDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDFIRST, (WPARAM)_hContact, NULL);
			hDbEvent != NULL;
			hDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDNEXT, (WPARAM)hDbEvent, NULL))
		{

			// get current event's data size
			dbei.cbBlob = CallService(MS_DB_EVENT_GETBLOBSIZE, (WPARAM)hDbEvent, NULL);
			if(dbei.cbBlob > 0) {

				// make sure data buffer is large enough
				if(dbei.cbBlob > cbEventBuf) {
					if(pbEventBuf) mir_free(pbEventBuf);
					pbEventBuf = (PBYTE)mir_alloc(dbei.cbBlob + 1);
					if(pbEventBuf == NULL) {
						MessageBoxA(NULL, "mir_alloc(pbEventBuf + 5) == NULL", "Error", 0);
						break;
					}

					dbei.pBlob = pbEventBuf;
					cbEventBuf = dbei.cbBlob;
				}

				// read event from database
				if(!CallService(MS_DB_EVENT_GET, (WPARAM)hDbEvent, (LPARAM)&dbei)) {

					// new buffer for base64 encoded data
					cbNewBase64Data = Base64EncodeGetRequiredLength(dbei.cbBlob, BASE64_FLAG_NOCRLF);
					if(cbNewBase64Data > cbBase64Data) {
						if(pBase64Data) mir_free(pBase64Data);
						pBase64Data = (LPSTR)mir_alloc(cbNewBase64Data + 5);
						if(pBase64Data == NULL) {
							MessageBoxA(NULL, "mir_alloc(cbNewBase64Data + 5) == NULL", "Error", 0);
							break;
						}
						cbBase64Data = cbNewBase64Data;
					}
					
					// encode data
					if(Base64Encode(dbei.pBlob, dbei.cbBlob, pBase64Data, &cbNewBase64Data, BASE64_FLAG_NOCRLF)) {
						pBase64Data[cbNewBase64Data] = 0;

					
						xmlEvent = new TiXmlElement("evt");
						if(xmlEvent) {
							xmlEvent->SetAttribute("type", dbei.eventType);
							xmlEvent->SetAttribute("time", dbei.timestamp);
							xmlEvent->SetAttribute("flag", dbei.flags);

							xmlText = new TiXmlText(pBase64Data);
							xmlEvent->LinkEndChild(xmlText);

							// find module
							for(xmlModule = _xmlNode->FirstChild(); xmlModule != NULL; xmlModule = xmlModule->NextSibling()) {
								if(!mir_stricmp(((TiXmlElement*)xmlModule)->Attribute("key"), dbei.szModule)) break;
							}
							// create new module
							if(!xmlModule) {
								xmlModule = _xmlNode->InsertEndChild(TiXmlElement(XKEY_MOD));
								if(!xmlModule) break;
								((TiXmlElement*)xmlModule)->SetAttribute("key", dbei.szModule);
							}

							xmlModule->LinkEndChild(xmlEvent);
							dwNumEventsAdded++;
							xmlEvent = NULL; // avoid final deleting
						}
					}
				}
			}
		}
	}
	catch(...) {
		// fuck, do nothing
		dwNumEventsAdded = 0;
	}

	if(pbEventBuf) mir_free(pbEventBuf);
	if(pBase64Data) mir_free(pBase64Data);
	if(xmlEvent) delete xmlEvent;

	return dwNumEventsAdded == dwNumEvents;
}

/***********************************************************************************************************
 * importing stuff
 ***********************************************************************************************************/

/**
 * name:	CountKeys
 * desc:	Counts the number of events and settings stored for a contact
 * params:	xmlContact	- the contact, who is the owner of the keys to count
 * return:	nothing
 **/
VOID CExImContactXML::CountKeys(DWORD &numSettings, DWORD &numEvents)
{
	TiXmlNode *xmod, *xkey;

	numSettings = numEvents = 0;
	for(xmod = _xmlNode->FirstChild(); 
		xmod != NULL; 
		xmod = xmod->NextSibling(XKEY_MOD)
	) {
		for(xkey = xmod->FirstChild();
			xkey != NULL;
			xkey = xkey->NextSibling()
		) {
			if(!mir_stricmp(xkey->Value(), XKEY_SET)) numSettings++;
			else numEvents++;
		}
	}
}

/**
 * name:	LoadXmlElemnt
 * class:	CExImContactXML
 * desc:	get contact information from database
 * param:	xContact	- TiXmlElement representing a contact
 * return:	ERROR_OK if successful or any other error number otherwise
 **/
INT CExImContactXML::LoadXmlElemnt(TiXmlElement *xContact)
{
	if(xContact == NULL) return ERROR_INVALID_PARAMS;

	// delete last contact
	DBFreeVariant(&_dbvUID);
	_hContact = INVALID_HANDLE_VALUE;

	_xmlNode = xContact;
	nick(xContact->Attribute("nick"));
	disp(xContact->Attribute("disp"));
	group(xContact->Attribute("group"));

	// is contact a metacontact
	if(_xmlNode->FirstChildElement(XKEY_CONTACT)) {
		TiXmlElement *xSub;
		
		proto(META_PROTO);

		// meta contact must be uniquelly identified by its subcontacts
		// the metaID may change during an export or import call
		for(xSub = xContact->FirstChildElement(XKEY_CONTACT); xSub != NULL; xSub = xSub->NextSiblingElement(XKEY_CONTACT)) {
			CExImContactXML vSub(_pXmlFile);
			if(vSub = xSub) {
				// identify metacontact by the first valid subcontact in xmlfile
				if(_hContact == INVALID_HANDLE_VALUE && vSub.handle() != INVALID_HANDLE_VALUE) {
					HANDLE hMeta = (HANDLE)CallService(MS_MC_GETMETACONTACT, (WPARAM)vSub.handle(), NULL);
					if(hMeta != NULL) {
						_hContact = hMeta;
						break;
					}
				}	
			}
		}
		// if no handle was found, this is a new meta contact
		_isNewContact = _hContact == INVALID_HANDLE_VALUE;
	}
	// entry is a default contact
	else {
		proto(xContact->Attribute("proto"));
		uidk(xContact->Attribute("uidk"));
		if(!proto()) {
			// check if this is the owner contact
			if(mir_stricmp(xContact->Value(), XKEY_OWNER))
				return ERROR_INVALID_PARAMS;
			_hContact = NULL;
			_xmlNode = xContact;
			return ERROR_OK;
		}

		if(uidk()) {
			LPCSTR pUID = xContact->Attribute("uidv");
			
			if(pUID != NULL) {
				switch(*(pUID++)) {
					case 'b':
						uid((BYTE)atoi(pUID));
						break;
					case 'w':
						uid((WORD)atoi(pUID));
						break;
					case 'd':
						uid((DWORD)atoi(pUID));
						break;
					case 's':
					case 'u':
						uid(pUID);
						break;
					default:
						uid((LPSTR)NULL);
						break;
				}
			}
		}
		// finally try to find contact in contact list
		findHandle();
	}
	return ERROR_OK;
}

/**
 * name:	ImportContact
 * class:	CExImContactXML
 * desc:	create the contact if neccessary and copy
 *			all information from the xmlNode to database
 * param:	none
 * return:	ERROR_OK on success or any other error number otherwise
 **/
INT CExImContactXML::ImportContact()
{
	TiXmlNode *xmod;

	// create the contact if not yet exists
	if(toDB() != INVALID_HANDLE_VALUE) {
		DWORD numSettings, numEvents;

		// count settings and events and init progress dialog
		CountKeys(numSettings, numEvents);
		_pXmlFile->_progress.SetSettingsCount(numSettings + numEvents);
		_pXmlFile->_numSettingsTodo += numSettings;
		_pXmlFile->_numEventsTodo += numEvents;

		// import all modules
		for(xmod = _xmlNode->FirstChild(); xmod != NULL; xmod = xmod->NextSibling(XKEY_MOD)) {
			// import module
			if(ImportModule(xmod) == ERROR_ABORTED) {
				// ask to delete new incomplete contact
				if(_isNewContact && _hContact != NULL) {
					INT result = MsgBox(NULL, MB_YESNO|MB_ICONWARNING, 
						_T("Question"), 
						_T("Importing a new contact was aborted!"), 
						_T("You aborted import of a new contact.\nSome information may be missing for this contact.\n\nDo you want to delete the incomplete contact?"));
					if(result == IDYES) {
						CallService(MS_DB_CONTACT_DELETE, (WPARAM)_hContact, NULL);
						_hContact = INVALID_HANDLE_VALUE;
					}
				}
				return ERROR_ABORTED;
			}
		}
		return ERROR_OK;
	}
	return ERROR_NOT_ADDED;
}

/**
 * name:	ImportNormalContact
 * class:	CExImContactXML
 * desc:	create the contact if neccessary and copy
 *			all information from the xmlNode to database.
 *			Remove contact from a metacontact if it is a subcontact
 * param:	none
 * return:	ERROR_OK on success or any other error number otherwise
 **/
INT CExImContactXML::ImportNormalContact()
{
	INT err = ImportContact();

	// remove contact from a metacontact
	if(err == ERROR_OK && CallService(MS_MC_GETMETACONTACT, (WPARAM)_hContact, NULL)) {
		CallService(MS_MC_REMOVEFROMMETA, NULL, (LPARAM)_hContact);
	}	
	return err;
}

/**
 * name:	Import
 * class:	CExImContactXML
 * desc:	create the contact if neccessary and copy
 *			all information from the xmlNode to database.
 *			Remove contact from a metacontact if it is a subcontact
 * param:	none
 * return:	ERROR_OK on success or any other error number otherwise
 **/
INT CExImContactXML::Import(BOOLEAN keepMetaSubContact)
{
	TiXmlElement *xContact = _xmlNode->FirstChildElement("CONTACT");
	INT result;

	// xml contact contains subcontacts?
	if(xContact) {

		// contact is a metacontact and metacontacts plugin is installed?
		if(isMeta()) {
			// create object for first sub contact
			CExImContactXML	vContact(_pXmlFile);
			LPTSTR pszNick;

			// the contact does not yet exist
			if(_isNewContact) {
				// import default contact as normal contact and convert to meta contact
				if(!(vContact = xContact)) {
					return ERROR_CONVERT_METACONTACT;
				}
				// import as normal contact
				result = vContact.ImportContact();
				if(result != ERROR_OK) return result;
				// convert default subcontact to metacontact
				_hContact = (HANDLE)CallService(MS_MC_CONVERTTOMETA, (WPARAM)vContact.handle(), NULL);
				if(_hContact == NULL) {
					_hContact = INVALID_HANDLE_VALUE;
					return ERROR_CONVERT_METACONTACT;
				}
				_pXmlFile->_numContactsDone++;
				// do not load first meta contact twice
				xContact = xContact->NextSiblingElement("CONTACT");
			}

			// load contact information
			ImportContact();

			// load all subcontacts
			do {
				// update progressbar and abort if user clicked cancel
				pszNick = Utf8ToTChar(xContact->Attribute("nick"));
				result = _pXmlFile->_progress.UpdateContact(_T("Sub Contact: %s (%S)"), pszNick, xContact->Attribute("proto"));
				if(pszNick) mir_free(pszNick);
				// user clicked abort button
				if(!result) break;

				if(vContact = xContact) {
					if(vContact.ImportMetaSubContact(this) == ERROR_ABORTED)
						return ERROR_ABORTED;
					_pXmlFile->_numContactsDone++;
				}
			}
			while(xContact = xContact->NextSiblingElement("CONTACT"));
			return ERROR_OK;
		}
		// import sub contacts as normal contacts
		return _pXmlFile->ImportContacts(_xmlNode);
	}

	// load contact information
	result = ImportContact();
	if(result == ERROR_OK && !keepMetaSubContact) {
		CallService(MS_MC_REMOVEFROMMETA, NULL, (LPARAM)_hContact);
	}

	return result;
}

/**
 * name:	ImportMetaSubContact
 * class:	CExImContactXML
 * desc:	create the contact if neccessary and copy
 *			all information from the xmlNode to database.
 *			Add this contact to an meta contact
 * param:	pMetaContact	- the meta contact to add this one to
 * return:	
 **/
INT CExImContactXML::ImportMetaSubContact(CExImContactXML * pMetaContact)
{
	INT err = ImportContact();

	// abort here if contact was not imported correctly
	if(err != ERROR_OK) return err;

	// check if contact is subcontact of the desired meta contact
	if((HANDLE)CallService(MS_MC_GETMETACONTACT, (WPARAM)_hContact, NULL) != pMetaContact->handle()) {
		// add contact to the metacontact (this service returns TRUE if successful)	
		err = CallService(MS_MC_ADDTOMETA, (WPARAM)_hContact, (LPARAM)pMetaContact->handle());
		if(err == FALSE) {
			// ask to delete new contact
			if(_isNewContact && _hContact != NULL) {
				LPTSTR ptszNick = Utf8ToTChar(nick());
				LPTSTR ptszMetaNick = Utf8ToTChar(pMetaContact->nick());
				INT result = MsgBox(NULL, MB_YESNO|MB_ICONWARNING, 
					_T("Question"), 
					_T("Importing a new meta subcontact failed!"), 
					_T("The newly created MetaSubContact '%s'\ncould not be added to MetaContact '%s'!\n\nDo you want to delete this contact?"),
					ptszNick, ptszMetaNick);
				if(ptszNick) mir_free(ptszNick);
				if(ptszMetaNick) mir_free(ptszMetaNick);
				if(result == IDYES) {
					CallService(MS_DB_CONTACT_DELETE, (WPARAM)_hContact, NULL);
					_hContact = INVALID_HANDLE_VALUE;
				}
			}
			return ERROR_ADDTO_METACONTACT;
		}
	}
	return ERROR_OK;
}

/**
 * name:	ImportModule
 * class:	CExImContactXML
 * desc:	interprete an xmlnode as module and add the children to database.
 * params:	hContact	- handle to the contact, who is the owner of the setting to import
 *			xmlModule	- xmlnode representing the module
 *			stat		- structure used to collect some statistics
 * return:	ERROR_OK on success or one other element of ImportError to tell the type of failure
 **/
INT CExImContactXML::ImportModule(TiXmlNode* xmlModule)
{
	TiXmlElement *xMod;
	TiXmlElement *xKey;
	LPCSTR pszModule;
	BOOLEAN isProtoModule;
	
	// check if parent is really a module
	if(!xmlModule || mir_stricmp(xmlModule->Value(), XKEY_MOD))
		return ERROR_INVALID_SIGNATURE;
	// convert to element
	if(!(xMod = xmlModule->ToElement()))
		return ERROR_INVALID_PARAMS;
	// get module name
	pszModule = xMod->Attribute("key");
	if(!pszModule || !*pszModule)
		return ERROR_INVALID_PARAMS;
	// ignore this protocol as it would cause trouble
	if(!mir_stricmp(pszModule, "Protocol"))
		return ERROR_OK;
	
	// check if the module to import is the contact's protocol module
	isProtoModule = !mir_stricmp(pszModule, proto()) || !mir_stricmp(pszModule, META_PROTO);

	for(xKey = xmlModule->FirstChildElement(); xKey != NULL; xKey = xKey->NextSiblingElement()) {
		// import setting
		if(!mir_stricmp(xKey->Value(), XKEY_SET)) {
			// just ignore all settings of protocol module to avoid errors
			if((isProtoModule && !IsContactInfo(xKey->Attribute("key"))) || ImportSetting(pszModule, xKey->ToElement()) == ERROR_OK)
				_pXmlFile->_numSettingsDone++;
			if(!_pXmlFile->_progress.UpdateSetting(_T("Settings: %S"), pszModule))
				return ERROR_ABORTED;
		}
		// import event
		else {
			INT error = ImportEvent(pszModule, xKey->ToElement());
			switch(error) {
				case ERROR_OK:
					_pXmlFile->_numEventsDone++;
					break;
				case ERROR_DUPLICATED:
					_pXmlFile->_numEventsDuplicated++;
					break;
			}
			if(!_pXmlFile->_progress.UpdateSetting(_T("Events: %S"), pszModule))
				return ERROR_ABORTED;
		}
	}
	return ERROR_OK;
}

/**
 * name:	ImportSetting
 * class:	CExImContactXML
 * desc:	interprete an setting representing xmlnode and write the corresponding setting to database.
 * params:	xmlModule	- xmlnode representing the module to write the setting to in the database
 *			xmlEntry	- xmlnode representing the setting to import
 * return:	ERROR_OK on success or one other element of ImportError to tell the type of failure
 **/
INT CExImContactXML::ImportSetting(LPCSTR pszModule, TiXmlElement *xmlEntry)
{
	DBCONTACTWRITESETTING cws;
	TiXmlText* xval;
	LPCSTR value;
	
	// validate parameter
	if(!xmlEntry || !pszModule || !*pszModule)
		return ERROR_INVALID_PARAMS;

	// validate value
	xval = (TiXmlText*)xmlEntry->FirstChild();
	if(!xval || xval->Type() != TiXmlText::TEXT)
		return ERROR_INVALID_VALUE;
	value = xval->Value();

	// init write structure
	cws.szModule = (LPSTR)pszModule;
	cws.szSetting = xmlEntry->Attribute("key");
	
	// convert data
	switch(value[0]) {
		case 'b':
			cws.value.type = DBVT_BYTE;
			cws.value.bVal = (BYTE)atoi(value + 1);
			break;
		case 'w':
			cws.value.type = DBVT_WORD;
			cws.value.wVal = (WORD)atoi(value + 1);
			break;
		case 'd':
			cws.value.type = DBVT_DWORD;
			cws.value.dVal = (DWORD)_atoi64(value + 1);
			break;
		case 's':
		case 'u':
			cws.value.type = DBVT_UTF8;
			cws.value.pszVal = (LPSTR)(value + 1);
			break;
		default:
			return ERROR_INVALID_TYPE;
	}
	// write value to db
	if(CallService(MS_DB_CONTACT_WRITESETTING, (WPARAM)_hContact, (LPARAM)&cws))
		return ERROR_NOT_ADDED;
	return ERROR_OK;
}

/**
 * name:	ImportEvent
 * class:	CExImContactXML
 * desc:	interprete an xmlnode and add the corresponding event to database.
 * params:	hContact	- handle to the contact, who is the owner of the setting to import
 *			xmlModule	- xmlnode representing the module to write the setting to in the database
 *			xmlEvent	- xmlnode representing the event to import
 * return:	ERROR_OK on success or one other element of ImportError to tell the type of failure
 **/
INT CExImContactXML::ImportEvent(LPCSTR pszModule, TiXmlElement *xmlEvent)
{
	DBEVENTINFO	dbei;
	TiXmlText* xmlValue;
	LPCSTR tmp;
	INT cbSrc;

	if(!xmlEvent || !pszModule || !*pszModule)
		return ERROR_INVALID_PARAMS;

	dbei.pBlob = NULL;
	dbei.cbBlob = NULL;

	if(stricmp(xmlEvent->Value(), "evt"))
		return ERROR_NOT_ADDED;

	// timestamp must be valid
	xmlEvent->Attribute("time", (LPINT)&dbei.timestamp);
	if(dbei.timestamp == 0) return ERROR_INVALID_TIMESTAMP;

	xmlValue = (TiXmlText*)xmlEvent->FirstChild();
	if(!xmlValue || xmlValue->Type() != TiXmlText::TEXT)
		return ERROR_INVALID_VALUE;
	tmp = xmlValue->Value();
	if(!tmp || tmp[0] == 0)
		return ERROR_INVALID_VALUE;

	cbSrc = strlen(tmp);
	dbei.cbBlob = Base64DecodeGetRequiredLength(cbSrc);
	dbei.pBlob = (PBYTE)_alloca(dbei.cbBlob + 1);
	if(dbei.pBlob != NULL) {
		if(Base64Decode(tmp, cbSrc, dbei.pBlob, (LPINT)&dbei.cbBlob)) {
			INT hEvent;

			// event owning module
			dbei.cbSize = sizeof(dbei);
			dbei.szModule = (LPSTR)pszModule;

			xmlEvent->Attribute("type", (LPINT)&dbei.eventType);
			xmlEvent->Attribute("flag", (LPINT)&dbei.flags);
			if(dbei.flags == 0) dbei.flags = DBEF_READ;

			// search for existing event to avoid duplicates if contact exists in database
			if(!_isNewContact && IsDuplicateEvent(_hContact, dbei)) {
				return ERROR_DUPLICATED;
			}

			hEvent = CallService(MS_DB_EVENT_ADD, (WPARAM)_hContact, (LPARAM)&dbei);
			if(hEvent) return ERROR_OK;
		}
	}
	return ERROR_NOT_ADDED;
}

} // namespace NExImport